package org.naxx.security.api.impl;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.apache.log4j.Logger;
import org.naxx.security.api.NaxxRepositoryManager;
import org.naxx.security.api.Repository;
import org.naxx.security.common.BeanNames;
import org.naxx.security.common.Configuration;
import org.naxx.security.common.UuidGen;
import org.naxx.security.compiler.XacmlCompiler;
import org.naxx.security.datatype.DataTypeDefinitionLoader;
import org.naxx.security.datatype.DataTypeResolver;
import org.naxx.security.datatype.DataTypeValueResolver;
import org.naxx.security.function.FunctionTypeDefinitionLoader;
import org.naxx.security.function.FunctionTypeResolver;
import org.naxx.security.naxxlet.NaxxletLoader;
import org.naxx.security.parser.ApplyParser;
import org.naxx.security.parser.PolicyParser;
import org.naxx.security.parser.PolicySetParser;
import org.naxx.security.parser.RequestParser;
import org.naxx.security.parser.RuleParser;
import org.naxx.security.parser.TargetParser;
import org.naxx.security.parser.datatype.DataTypeParser;
import org.naxx.security.parser.funcdef.FunctionTypeDefinitionParser;
import org.naxx.security.parser.validation.SchemaValidator;
import org.naxx.security.util.ClasspathUtil;
import org.naxx.security.util.FileSystemUtil;
import org.naxx.security.xgen.parts.XConditionGenerator;
import org.springframework.context.ApplicationContext;

@SuppressWarnings("unchecked")
public final class RepositoryImpl implements Repository {
	
	private static final Logger logger = Logger.getLogger(RepositoryImpl.class);
	
	private final Configuration configuration;
	private final Map<Class, Object> services;
	
	public RepositoryImpl(ApplicationContext applicationContext) {
		this.services = new HashMap<Class, Object>();
		Properties props = new Properties();
		InputStream in = null;
		try {
			in = ClasspathUtil.getResourceFromClasspath("project.properties");
			props.load(in);
			this.configuration = new Configuration(props);
		} catch (IOException e) {
			throw new IllegalStateException("Unable to load 'project.properties' file", e);
		} finally {
			FileSystemUtil.close(in);
		}
		this.init(applicationContext);
	}
	

	@Override
	public <T> T getService(Class<T> clazz) {
		Object obj = this.services.get(clazz);
		if(obj != null) {
			return clazz.cast(obj);
		}
		logger.warn("Repository could not find service for class '" + clazz.getName() + "'," +
				" check method init() to see if registration has been performed.");
		return null;
	}

	@Override
	public <T> void registerService(T extension, Class<T> service) {
		if(this.services.containsKey(service)) {
			// for debugging purposes
			throw new IllegalStateException("Can not register same service twice '" 
					+ service.getCanonicalName() + "'.");
		}
		this.services.put(service, extension);
	}

	@Override
	public Configuration getSystemConfiguration() {
		return this.configuration;
	}
	
	private void init(ApplicationContext ctx) {
		this.services.put(PolicySetParser.class, ctx.getBean(BeanNames.POLICY_SET_PARSER));
		this.services.put(PolicyParser.class, ctx.getBean(BeanNames.POLICY_PARSER));
		this.services.put(TargetParser.class, ctx.getBean(BeanNames.TARGET_PARSER));
		this.services.put(RuleParser.class, ctx.getBean(BeanNames.RULE_PARSER));
		this.services.put(ApplyParser.class, ctx.getBean(BeanNames.APPLY_PARSER));
		this.services.put(FunctionTypeDefinitionParser.class, ctx.getBean(BeanNames.FUNCTION_TYPE_DEFINITION_PARSER));
		this.services.put(DataTypeParser.class, ctx.getBean(BeanNames.DATA_TYPE_PARSER));
		this.services.put(RequestParser.class, ctx.getBean(BeanNames.REQUEST_PARSER));
		this.services.put(DataTypeDefinitionLoader.class, ctx.getBean(BeanNames.DATA_TYPE_DEFINITION_LOADER));
		this.services.put(DataTypeResolver.class, ctx.getBean(BeanNames.DATA_TYPE_RESOLVER));
		this.services.put(DataTypeValueResolver.class, ctx.getBean(BeanNames.DATA_TYPE_VALUE_RESOLVER));
		this.services.put(FunctionTypeDefinitionLoader.class, ctx.getBean(BeanNames.FUNCTION_TYPE_DEFINITION_LOADER));
		this.services.put(FunctionTypeResolver.class, ctx.getBean(BeanNames.FUNCTION_TYPE_RESOLVER));
		this.services.put(SchemaValidator.class, ctx.getBean(BeanNames.SCHEMA_VALIDATOR));
		this.services.put(UuidGen.class, ctx.getBean(BeanNames.UUID_GENERATOR));
		this.services.put(XacmlCompiler.class, ctx.getBean(BeanNames.XACML_COMPILER));
		this.services.put(XConditionGenerator.class, ctx.getBean(BeanNames.X_CONDITION_GENERATOR));
		this.services.put(NaxxRepositoryManager.class, ctx.getBean(BeanNames.NAXX_REPOSITORY_MANAGER));
		this.services.put(NaxxletLoader.class, ctx.getBean(BeanNames.NAXXLET_LOADER));
	}
}
